深入分析MySQL行锁加锁规则 您所在的位置:网站首页 mysql 数据加锁 深入分析MySQL行锁加锁规则

深入分析MySQL行锁加锁规则

2023-02-24 21:47| 来源: 网络整理| 查看: 265

之前的一篇文章《深入理解MySQL的MVCC原理》中总结了一下MySQL中的MVCC,它主要利用隐藏字段、版本链、ReadView来实现,可以用来更好地解决多个事务的并发【读+写】问题,但是如果在多个事务并发【写+写】的情况下,就必须要用到锁了,一般情况下,数据库的锁都是在有数据库操作的过程中自动添加的。

MySQL提供了很多种锁:

Server层实现了全局锁和元数据锁。

数据引擎中,MyISAM、Memory等存储引擎实现了表锁(且只支持表锁),BerkeleyDB存储引擎实现了页级锁,InnoDB实现了行锁和表锁。

在这里插入图片描述

平时最常见、最常用的就是InnoDB的行锁,所以在这里主要来探索一下InnoDB的行锁。

顾名思义,行锁就是给数据库表中每行数据加锁,行锁是加在索引上的,比如某个表中id字段是主键,如果给id=2这条记录加锁,那这把锁是加在主键索引(聚簇索引)上的。如果为某个没有索引的字段加锁,最终会在主键索引上锁住所有的记录。在InnoDB的实现中,行锁有3中主要的算法:

Record Lock:对单个行记录上锁,这里我们称为记录锁。

Gap Lock:对不包含真实存在记录的某一个间隙/范围加锁,这里我们称它为间隙锁,间隙锁只有一个目的就是在RR、SERIALIZABLE隔离级别下为了防止其他事务插入数据。假如一个索引有2、4、5、9、12 五个值,那该索引可能被间隙锁锁的范围为(-∞ , 2),(2 , 4),(4 , 5),(5 , 9),(9 , 12),(12 , +∞)。

Next-Key Lock:相当于Record Lock+Gap Lock,对【某一个行记录】和【这条记录与它前一条记录之间的范围/间隙】都上锁,这里我们称它为邻键锁。假如一个索引有2、4、5、9、12 五个值,那该索引可能被邻键锁锁的范围为(-∞ , 2],(2 , 4],(4 , 5],(5 , 9],(9 , 12],(12 , +∞)。在InnoDB中,加锁的基本单位是Next-Key Lock,只不过在某些特殊情况下会退化为 Record Lock 或者 Gap Lock。

有些同学都会问:“这个sql语句会加什么锁”,其实这是一个伪命题,因为一个语句需要加什么锁受到很多方面的影响。在实际场景中,行级锁加锁规则比较复杂,不同的查询条件,不同的索引,不同的隔离级别,加锁的情况可能不同,甚至不同版本的MySQL加锁规则也可能会稍有差异。

这里我们围绕下面两个问题,记录一下MySQL在默认的RR隔离级别下的行锁加锁情况(在RC隔离级别下加锁的情况跟在RR隔离级别下差不多,不同的是RC隔离级别下只会对记录加Record Lock,不会加Gap Lock 和 Next-Key Lock),暂时不考虑任何操作对表加的意向锁。当前mysql版本:8.0.27。

查询条件为主键索引、唯一索引、普通索引对应的字段时,会在哪些索引上加锁?

当查询条件是等值查询或范围查询,查询结果存在或不存在时,分别会给索引上的哪些记录加锁(锁住了哪些范围的数据)?

当前MySQL版本:8.0.27,创建和初始化表:

CREATE TABLE `t_lock_test` ( `id` int NOT NULL, `mobile` varchar(255) DEFAULT NULL, `name` varchar(255) DEFAULT NULL, `age` int DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `idx_mobile` (`mobile`) USING BTREE, KEY `idx_name` (`name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; INSERT INTO `t_lock_test` VALUES (2, '17118168721', 'Bob', 31); INSERT INTO `t_lock_test` VALUES (4, '15373838350', 'Bob', 30); INSERT INTO `t_lock_test` VALUES (5, '13785078432', 'Kara', 20); INSERT INTO `t_lock_test` VALUES (9, '18901970832', 'Anna', 30); INSERT INTO `t_lock_test` VALUES (12, '17837938413', 'Kara', 25); 复制代码

我们创建了一张名为t_lock_test的表,id为主键,mobile为唯一索引,name为非唯一索引,age没有索引。并且插入了5条记录:

这张表对应的3个索引树为: 在这里插入图片描述

下面为了简洁,只画出索引的叶子结点,比如主键索引简画为:

这里说明一点:任何情况下的行锁,只会对实际存在的记录(比如上面主键索引中id=12、4、5、9、12的记录)和supremum pseudo-record(最大界限伪记录,下面会说到)加锁。

1 查询条件为主键索引 1.1 等值查询记录存在时,在索引的什么位置加什么锁?为什么?

例1:事务5564执行 select * from t_lock_test where id=5 for update; ,通过performance_schema.data_locks表查看MySQL事务获取锁的情况,发现在主键索引上对id=5的记录加上了记录锁:

在索引上锁的范围如下:

此时另一个事务5565执行 select * from t_lock_test where id=5 for update;被阻塞了,等待锁失败: 在这里插入图片描述

因为这个事务5565也要获取主键索引上id=5的记录锁:

这里可以得出结论:查询条件为主键索引时,如果查询条件是等值查询且记录存在,只对符合条件的记录加记录锁(只锁符合条件的记录)。

1.2 等值查询记录不存在时,在索引的什么位置加什么锁?为什么?

例1:事务5566执行 select * from t_lock_test where id=7 for update;,加锁情况如下:

在主键索引对id=9的记录加间隙锁(包含7的“间隙”为(5,9),加锁的基本单位是next-key lock,所以对id=9的记录加next-key lock,因为查询条件不包含9,所以退化为gap锁):

此时其他事务执行insert into t_lock_test values(8,'13118161267','Bob',31);也会被阻塞,因为区间(5,9)已经被锁住了,所以此时插入id为6、7、8的记录都会被阻塞。和RC隔离级别不同的是,在RC隔离级别下,不会对id=9的记录加间隙锁。

这里可以得出结论:查询条件为主键索引时,如果查询条件是等值查询且记录不存在,会对查询条件所在间隙的下一条记录加间隙锁(相当于锁的范围就是查询条件所在间隙)。

例2:这里扩展一个例子,当事务执行select * from t_lock_test where id=13 for update;时,加锁情况如下:

发现对supremum pseudo-record这条记录加了邻键锁,supremum pseudo-record 是最大界限伪记录(相当于正无穷+∞), 对应的还有最小界限伪记录infimum pseudo-record(相当于负无穷-∞)。因为supremum pseudo-record的值是最大正无穷,因此它的间隙锁和邻键锁可以看做是一样的(在t_lock_test表中supremum pseudo-record的间隙锁和邻键锁锁的范围都是(12,+∞) ),加锁的基本单位是邻键锁,因此就无需降级了,所以后面只要给supremum pseudo-record加锁,基本上都是邻键锁。

1.3 范围查询记录存在时,在索引的什么位置加什么锁?为什么?

例1:当事务5682分别执行 select * from t_lock_test where id>4 and id 4 and id 4 and id 4 and id 4 and id 4 and id 5 and id4 and id 7 and id



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有